package com.geek.video.service.impl;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.geek.video.enums.MethodEnum;
import com.geek.video.enums.PlayTypeEnum;
import com.geek.video.enums.ResponseEnum;
import com.geek.video.enums.WayEnum;
import com.geek.video.exception.GlobalException;
import com.geek.video.mapper.VipAnalysisDao;
import com.geek.video.domain.VipAnalysis;
import com.geek.video.service.VipAnalysisService;
import com.geek.video.service.dto.VipAnalysisDTO;
import com.geek.video.utils.BeanAndMapTransform;
import com.geek.video.web.response.ParsingTheUrlRes;
import com.geek.video.web.vo.M3U8Vo;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * vip解析表(VipAnalysis)表服务实现类
 *
 * @author qinxh
 * @since 2021-01-25 16:33:48
 */
@Service("vipAnalysisService")
public class VipAnalysisServiceImpl implements VipAnalysisService {

    private static final Logger logger = LoggerFactory.getLogger(VipAnalysisServiceImpl.class);

    @Resource
    private VipAnalysisDao vipAnalysisDao;

    private final ObjectMapper objectMapper;

    public VipAnalysisServiceImpl(ObjectMapper objectMapper) {

        // 属性为 空（“”） 或者为 NULL 都不序列化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        this.objectMapper = objectMapper;
    }

    @Override
    public VipAnalysisDTO queryById(Integer id) {

        if (id == null) {
            throw new GlobalException(ResponseEnum.PARAM_EMPTY);
        }

        VipAnalysis vipAnalysis = vipAnalysisDao.queryById(id);
        VipAnalysisDTO dto = new VipAnalysisDTO();
        if (vipAnalysis == null) {
            return dto;
        }

        BeanUtils.copyProperties(vipAnalysis, dto);
        return dto;
    }

    @Override
    public List<VipAnalysisDTO> queryAll(VipAnalysisDTO vipAnalysisDTO) {

        List<VipAnalysis> list = vipAnalysisDao.queryAll(BeanAndMapTransform.bean2Map(vipAnalysisDTO));

        return Optional.ofNullable(list).orElse(Collections.emptyList()).stream().map(m -> {
            VipAnalysisDTO dto = new VipAnalysisDTO();
            BeanUtils.copyProperties(m, dto);
            return dto;
        }).collect(Collectors.toList());
    }

    @Override
    public VipAnalysisDTO insert(VipAnalysisDTO vipAnalysisDTO) {

        if (vipAnalysisDTO == null) {
            throw new GlobalException(ResponseEnum.PARAM_EMPTY);
        }

        VipAnalysis v = new VipAnalysis();
        BeanUtils.copyProperties(vipAnalysisDTO, v);
        vipAnalysisDao.insert(v);

        vipAnalysisDTO.setId(v.getId());
        return vipAnalysisDTO;
    }

    @Override
    public VipAnalysisDTO update(VipAnalysisDTO vipAnalysisDTO) {

        if (vipAnalysisDTO == null || vipAnalysisDTO.getId() == null) {
            throw new GlobalException(ResponseEnum.PARAM_EMPTY);
        }
        VipAnalysis v = new VipAnalysis();
        BeanUtils.copyProperties(vipAnalysisDTO, v);
        vipAnalysisDao.update(v);

        return this.queryById(v.getId());
    }

    @Override
    public Boolean deleteById(Integer id) {

        return this.vipAnalysisDao.deleteById(id) > 0;
    }

    @Override
    @Cacheable(cacheNames = "writeCache", cacheManager = "writeCacheManager", key = "#root.methodName +'-'+ #url", unless = "#result == null")
    public ParsingTheUrlRes parsingTheVip(String url) {

        List<VipAnalysisDTO> list = this.queryAll(new VipAnalysisDTO());
        if (CollectionUtils.isEmpty(list) || list.stream().allMatch(f -> f.getWay() == null)) {
            logger.error("没有可用的解析源或解析源解析方式未配置");
            return null;
        }
        // 1. 获取到被解析的 url 网站来源
        AtomicReference<String> playType = new AtomicReference<>(PlayTypeEnum.UNKNOWN.getMessage());
        Arrays.stream(PlayTypeEnum.values()).filter(f ->
                url.contains(f.getEigenvalue())).findFirst().ifPresent(p -> playType.set(p.getMessage()));
        // 2. 升序排序，按优先级解析
        list.sort(Comparator.comparing(VipAnalysisDTO::getPriority));
        ParsingTheUrlRes res = new ParsingTheUrlRes();
        for (VipAnalysisDTO v : list) {
            switch (WayEnum.getWayEnumByCode(v.getWay())) {
                case JSON:
                    try {
                        // 2.1 区分请求模式
                        String text = null;
                        if (v.getMethod().equals(MethodEnum.GET.getMethod())) {
                            text = Jsoup.connect(v.getUrl() + url).get().text();
                        }
                        if (v.getMethod().equals(MethodEnum.POST.getMethod())) {
                            text = Jsoup.connect(v.getUrl()).data("url", url).post().text();
                        }
                        // 2.3 没数据返回，继续用其他的播放源解析
                        if (StringUtils.isEmpty(text)) {
                            continue;
                        }
                        M3U8Vo m3U8Vo = objectMapper.readValue(text, M3U8Vo.class);
                        // 2.4 解析不成功，继续用其他的播放源解析
                        if (!m3U8Vo.getCode().equals(ResponseEnum.SUCCESS.getCode())) {
                            continue;
                        }
                        // 2.5 解析成功，直接退出方法
                        res.setPlayUrl(m3U8Vo.getUrl());
                        res.setPlayName(Optional.ofNullable(m3U8Vo.getTitle()).orElse("默认标题"));
                        res.setPlayType(playType.get());
                        logger.info("使用的解析源是：{}", v.getUrl());
                        return res;
                    } catch (IOException e) {
                        logger.error("json解析失败：{}", e.getMessage());
                        continue;
                    }
                case CRAWLER:
                    // TODO
                    continue;
                default:
                    logger.error("无此解析方式。对应枚举值为：{}", v.getWay());
            }
        }

        return null;
    }
}